'''
Created on 2011-12-5

@author: Administrator
'''
import logging
import datetime
import servertime
import task
from db import dbprotocol 
from taskflow.flow import TaskFlow
from taskflow.taskflowtemplate import TaskflowTemplate, NOT_TRIGGER, WAITING_TRIIGGERING


class TaskFlowManager():
    def __init__(self, scheduler, db_manager, result_processor):
        
        self.scheduler = scheduler
        self.db_manager = db_manager
        self.result_processor = result_processor
#        self.task_list = {}
        self.taskid_to_flow = {}
        self.flow_list = {}
        self.template_list = {}
        self.pending_task_flow = {}
        self.taskflow_index = 0
    

    def Init(self):
        
        try:
            self.scheduler.AddTaskFinishHander(self.HandleTaskFinish)
            all_task_flow_template = self.db_manager.LoadAllTaskFlow()
            self.LoadTaskflowTemplateFromDB(all_task_flow_template)
            
            self.db_manager.RegisterProtocol(dbprotocol.get_taskflowtemplate_respond,
                                             self.UpdateTaskFlowTemplateRespond)
            self.db_manager.RegisterProtocol(dbprotocol.insert_taskflow_respond, 
                                             self.HandleInsertTaskflow)
        except Exception as msg:
            logging.exception(msg)
            
            
    def CreateTaskflow(self, templateid):
        if templateid not in self.template_list:
            logging.warning('%d id not in template list' % templateid)
            return False
        
        self.taskflow_index += 1
        template  = self.template_list[templateid]
        tf = TaskFlow(self.taskflow_index, 
                      template.id, 
                      template.node_list.values(),
                      template)
        
        tf.status = task.WAITINGTORUN
        
        self.pending_task_flow[self.taskflow_index] = tf

        self.InsertTaskflowToDb(tf)
        
        return tf


    def InsertTaskflowToDb(self, taskflow):
        '''
        insert task flow info to DB.
        '''
        request = dbprotocol.InsertTaskFlowRequest()
        request.taskid = taskflow.index
        request.templateid = taskflow.templateid
        request.status = task.WAITINGTORUN
        self.db_manager.PutRequest(request) 


    def HandleInsertTaskflow(self, respond):
        if not respond.result:
            self.flow_list.pop(respond.flowid)
            logging.warn('task flow insert db fail id : %d', respond.flowid)
            return
        
        task_flow = self.pending_task_flow.pop(respond.taskid)
        task_flow.dbid = respond.dbid
        self.flow_list[respond.dbid] = task_flow
           
           
    def UpdateTaskFlow(self, taskflow):
        '''
        update task flow info to DB.
        '''
        request = dbprotocol.UpdateTaskFlowRequest()
        request.taskid = taskflow.dbid
        request.task_flow_data = {'StartTime':taskflow.begin_run_time,
                        'EndTime':taskflow.end_time,
                        'ResultStatusId':taskflow.result,
                        'TaskFlowStatusId':taskflow.status,
                        'ResultMessage':taskflow.result_message}
        for v in taskflow.node_list.itervalues():
            if v.status == NOT_TRIGGER:
                request.node_datas.append({'NodeName':v.name, 'Status':v.status, 'TaskFlowResultId':taskflow.dbid})
            else:
                request.node_datas.append({'NodeName':v.name, 'Status':v.status, 'TaskFlowResultId':taskflow.dbid, 'TaskId':v.task_db_id})
        self.db_manager.PutRequest(request)
    


    def HandleTaskFinish(self, task):
        if task.tempid not in self.taskid_to_flow:
            return

        tf  = self.taskid_to_flow[task.tempid]
        self.taskid_to_flow.pop(task.tempid)
        tf.OnTaskFinish(task.tempid, task.result, task.id, task.begin_run_time, task.end_time, task.result_message)

        if tf.IsFinish():
            self.TaskFlowFinish(tf)
            return

        for v in tf.GetTriggerNode():
            self.TriggerTaskInFlow(tf, v)


    def TaskFlowFinish(self, finish_task):
        finish_task.end_time = datetime.datetime.now()
        self.UpdateTaskFlow(finish_task)
        self.flow_list.pop(finish_task.dbid)
        self.result_processor.PutTaskFlowResult(finish_task)
        
        
    def TriggerTaskInFlow(self, taskflow, node):
        result, task = self.scheduler.NewTask(node.templateid)
        if result == False:
            taskflow.CreateTaskFail(node.name)
            return
        taskflow.SetTaskID(node.name, task.tempid)
        self.taskid_to_flow[task.tempid] = taskflow

    
    def TriggerTaskFlow(self, task_flow_id):
        '''
        rpc trigger.
        '''
        if task_flow_id not in self.template_list:
            logging.info('Load task flow template...')
            self.UpdataTaskFlowTemplate(task_flow_id)
            return True
        try:
            template = self.template_list[task_flow_id]
            self.CreateTaskflow(template.id)
        except Exception as msg:
            logging.exception(msg)
            return False
        else:
            return True
        
        
    def UpdataTaskFlowTemplate(self, task_flow_template_id):
        request = dbprotocol.GetTaskFlowTemplateInfo()
        request.templateid = task_flow_template_id
        self.db_manager.PutRequest(request)
        return True
    
    
    def UpdateTaskFlowTemplateRespond(self, respond):
        '''
        reload task flow template from db.
        '''
        if not respond.result:
            logging.warn('Load task flow template {0} fail!'.format(respond.templateid))
            return 
        
        task_flow_templates = respond.info
        self.LoadTaskflowTemplateFromDB(task_flow_templates)
        
        
        
    def TerminatedTaskFlow(self, task_flow_id):
        '''
        Terminate task flow
        '''
        if task_flow_id not in self.flow_list:
            logging.info('The task flow need to be terminate is not in flow list.')
            return False
        
        task_flow = self.flow_list[task_flow_id]
        if task_flow.status != task.RUNING:
            logging.info('The task flow need to be terminate not run.')
            return False
        
        
        for v in task_flow.node_list.itervalues():
            if v.taskid:
                t = self.scheduler.GetTaskFromTempId(v.taskid)
                if t:
                    self.scheduler.TerminatedTask(t.id)
            if v.status == WAITING_TRIIGGERING:
                v.status = NOT_TRIGGER
        return True
    
    
    def RemoveTaskFlow(self, task_flow_id):
        if task_flow_id in self.flow_list:
            del self.flow_list[task_flow_id]
            logging.info('Delete task flow template {0}'.format(task_flow_id))
            return True
        return False


    def LoadTaskflowTemplateFromDB(self, all_task_flow_template):
        '''
        Load task flow template from DB.
        '''
        if not all_task_flow_template:
            return
        
        for template_id, info in all_task_flow_template.iteritems():
            try:
                nodes_info = info['node_info']
                template_info = info['template_info']
                trigger_time = self.scheduler.DealTriggerTime(template_info)
                template = TaskflowTemplate(template_info.Id,
                                            template_info.Name, 
                                            trigger_time, 
                                            template_info.NextRunTime, 
                                            template_info.EmailReceiver,
                                            nodes_info)
                if template.GetNextRunTime() is None:
                    template.SetNextRunTime(servertime.current_time)
                self.template_list[template_id] = template
                logging.info('load task flow template %d success' % template_id)
            except Exception as msg:
                logging.info('Load task flow template %d fail!' % template_id)
                   

    def Loop(self):
        now = servertime.current_time
        for template in self.template_list.values():
            if template.next_run_time is None:
                continue
            if template.next_run_time < now:
                self.taskflow_index += 1
                self.CreateTaskflow(template.id)
                template.SetNextRunTime(now)
        
        for flow_task in self.flow_list.values():
            if flow_task.status == task.WAITINGTORUN:
                first_trigger_nodes = flow_task.GetFirstTriggerNode()
                for node in first_trigger_nodes:
                    self.TriggerTaskInFlow(flow_task, node)
                flow_task.status = task.RUNING
                flow_task.begin_run_time = datetime.datetime.now()
            elif flow_task.status == task.CANCELED:
                self.TaskFlowFinish(flow_task)
                

    
    
    